<?php
/**
 * Title
 * Created by Jason.
 * User: Administrator
 * Date: 2019/12/9 0009
 * Time: 下午 5:41
 */

namespace app\index\controller;


class RedisLock
{
    //单据锁redis key模板
    const REDIS_LOCK_KEY_TEMPLATE = 'order_lock_%s';

    //单据锁默认超时时间（秒）
    const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 3;

    //redis 链接配置参数
    const REDIS_CONFIG_HOST = '127.0.0.1';
    const REDIS_CONFIG_PORT = '6379';

    //用于生成唯一的锁ID的redis key
    const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id';

    //返回redis链接对象
    static protected function getRedisConnect($strIp = self::REDIS_CONFIG_HOST, $intPort = self::REDIS_CONFIG_PORT)
    {
        $redis_connect = new \Redis();
        $redis_connect->connect($strIp, $intPort);
        return $redis_connect;
    }

    //生成锁唯一ID（Redis incr生成指定位数唯一ID）
    static protected function generateUniqueLockId()
    {
        return self::getRedisConnect()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY);
    }

    /**
     * 生成redis锁并返回锁id
     * author Jason
     * Date 2019/12/9 0009 下午 5:49
     * @param $intOrderId  业务订单id
     * @param int $intExpireTime 有效期
     * @return bool|void  返回锁id/false
     */
    static public function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
    {

        //校验订单id，有效期时间参数
        if (empty($intOrderId) || $intExpireTime <= 0) {
            return false;
        }

        //redis链接对象
        $redis_connect = self::getRedisConnect();

        //生成唯一锁ID，解锁需持有此ID
        $intUniqueLockId = self::generateUniqueLockId();

        //根据模板，结合单据ID，生成唯一Redis key（一般来说，单据ID在业务中系统中唯一的）
        $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);

        //加锁（通过Redis setnx指令实现，从Redis 2.6.12开始，通过set指令可选参数也可以实现setnx，同时可原子化地设置超时时间）
        $bolRes = $redis_connect->set($strKey, $intUniqueLockId, ['nx', 'ex' => $intExpireTime]);

        //加锁成功返回锁ID，加锁失败返回false
        return $bolRes ? $intUniqueLockId : $bolRes;
    }

    static public function releaseLock($intOrderId, $intLockId)
    {
        //校验订单id，redis锁id
        if (empty($intOrderId) || empty($intLockId)) {
            return false;
        }

        //redis链接对象
        $redis_connect = self::getRedisConnect();

        //生成Redis key
        $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);

        //监听Redis key防止在【比对lock id】与【解锁事务执行过程中】被修改或删除
        //提交事务后会自动取消监控，其他情况需手动解除监控
        $redis_connect->watch($strKey);
        $state = false;
        if ($intLockId == $redis_connect->get($strKey)) {
            $redis_connect->multi()->del($strKey)->exec();
            $state = true;
        }
        $redis_connect->unwatch();
        return $state;

    }
}